package anaydis.animation.sort;

import anaydis.animation.core.AlgorithmAnimation;
import anaydis.animation.core.AnimationController;
import anaydis.animation.model.*;
import anaydis.sort.Sorter;
import anaydis.sort.gui.SorterListener;
import com.sun.istack.internal.NotNull;

import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class SortAnimation
        extends AlgorithmAnimation
        implements SorterListener
{
    protected List<Integer> array;

    private int size;

    private int boxFrom, boxTo;
    private int cmpFrom, cmpTo;
    private int swapFrom, swapTo;
    private int eqTo, eqFrom;

    private float swapStep;

    private final Sorter sorter;

    private SortDataModel data;

    public SortAnimation(@NotNull final Sorter sorter)
    {
        this.sorter = sorter;
    }

    public void setData(@NotNull final SortDataModel data)
    {
        if (data != this.data)
        {
            this.data = data;

            fillArray();

            controller.repaint();
        }
    }

    protected void fillArray()
    {
        array = data.create(size);
    }

    public void init(@NotNull final AnimationController controller, final int length)
    {
        this.controller = controller;
        this.size = length;
        this.paused = false;
        this.data = null;
        this.stopRequested = false;

        unmarkAll();
    }

    protected void doRun()
    {
        final List<Integer> copy = new ArrayList<Integer>(array.size());

        for(Integer value : array) {
            copy.add(value);
        }

        sorter.sort(COMPARATOR, copy);
    }

    protected void unmarkAll()
    {
        cmpTo = cmpFrom = -1;
        boxFrom = boxTo = -1;
        swapFrom = swapTo = -1;
        eqTo = eqFrom = -1;
    }

    public void paintArray(@NotNull final Graphics2D graphics)
    {
        final Rectangle clipBounds = graphics.getClipBounds();
        int width = clipBounds.width;
        int height = clipBounds.height;

        final int slotOffset = (width - SPACE) / size;

        paintArray(graphics, array, slotOffset, 0, height, swapFrom, swapTo, cmpFrom, cmpTo);

        drawBox(graphics, AlgorithmAnimation.BLUE, boxFrom, boxTo + 1 - boxFrom, slotOffset, height);

        finishPainting();
    }

    protected void paintArray(@NotNull final Graphics2D graphics, @NotNull final List<Integer> array,
                              final int slotOffset, int yoffset, int height,
                              int swapFrom, int swapTo, int cmpFrom, int cmpTo)
    {
        for (int i = 0; i < array.size(); i++)
        {
            if (i != swapFrom && i != swapTo)
            {
                Color color = AlgorithmAnimation.MAIN;
                Color lcolor = AlgorithmAnimation.LIGHT_MAIN;

                if (i == cmpFrom) {
                    color = AlgorithmAnimation.BLUE;
                    lcolor = AlgorithmAnimation.LIGHT_BLUE;
                }
                else if (i == cmpTo) {
                    color = AlgorithmAnimation.RED;
                    lcolor = AlgorithmAnimation.LIGHT_RED;
                }
                else if (i == eqTo || i == eqFrom) {
                    color = AlgorithmAnimation.GREEN;
                    lcolor = AlgorithmAnimation.LIGHT_GREEN;
                }

                drawSlot(graphics, array, color, lcolor, i, slotOffset, yoffset, height);
            }
        }

        drawSwap(graphics, swapStep, swapTo, swapFrom, slotOffset, height);
    }

    private void drawSwap(@NotNull final Graphics2D graphics, float swapStep, int swapTo, int swapFrom, int offset, int height)
    {
        if (swapTo != -1 && swapFrom != -1)
        {
            final float disp = (swapTo - swapFrom) * swapStep;
            int xFrom = Math.round(SPACE + offset * (swapFrom + disp));
            int xTo = Math.round(SPACE + offset * (swapTo - disp));
            drawSlot(graphics, array, AlgorithmAnimation.TMAIN, AlgorithmAnimation.LIGHT_TMAIN, xFrom, swapFrom, offset, 0, height);
            drawSlot(graphics, array, AlgorithmAnimation.TMAIN, AlgorithmAnimation.LIGHT_TMAIN, xTo, swapTo, offset, 0, height);
        }
    }

    private void drawBox(Graphics2D graphics, Color color, int from, int slots, int offset, int height)
    {
        if (from != -1) {
            graphics.setColor(color);
            graphics.setStroke(AlgorithmAnimation.normalStroke);

            graphics.drawRect(from * offset + 2, 0, slots * offset, height - 1);
        }
    }

    private void drawSlot(@NotNull final Graphics2D graphics, @NotNull final List<Integer> array,
                          Color color, Color lcolor, int i, int offset, int yoffset, int height)
    {
        int x = i * offset + SPACE;

        drawSlot(graphics, array, color, lcolor, x, i, offset, yoffset, height);
    }

    protected void drawSlot(@NotNull final Graphics2D graphics, @NotNull final List<Integer> array,
                            Color color, Color lcolor, int x, int i, int offset, int yoffset, int height)
    {
        if (i >= 0 && array.get(i) > 0)
        {
            height -= 2 * SPACE;

            final int ysize = (array.get(i) * height) / size;
            final int y = height + SPACE - ysize + yoffset;
            final int xsize = offset - SPACE;

            final RoundRectangle2D.Double rec = new RoundRectangle2D.Double(x, y, xsize, ysize, 8, 8);
            graphics.setColor(Color.BLACK);
            graphics.draw(rec);

            final GradientPaint grad = new GradientPaint(x, y, color, x + xsize, y + ysize, lcolor);
            graphics.setPaint(grad);
            graphics.fill(rec);
        }
    }

    protected void markBox(int from, int to)
    {
        boxFrom = from;
        boxTo = to;
    }

    public void greater(int i, int j)
    {
        cmpFrom = i;
        cmpTo = j;
        doSleep(1000);
        cmpFrom = cmpTo = -1;
    }

    public void equals(int i, int j)
    {
        eqTo = i;
        eqFrom = j;
        doSleep(1000);
        eqFrom = eqTo = -1;
    }

    public void swap(int i, int j)
    {
        if (speed == AnimationController.MAX_SPEED) {
            doSleep(3000);
        }
        else {
            swapFrom = i;
            swapTo = j;
            animating = true;
            int time = 3000;
            int steps = (AnimationController.MAX_SPEED - speed) / 3 + 1;
            int timeslot = time / steps;
            for (int step = 1; step <= steps; step++) {
                swapStep = (float) step / steps;
                doSleep(timeslot);
                time -= timeslot;
            }
            animating = false;
            if (time > 0) {
                doSleep(time);
            }

            array.set(swapTo, array.set(swapFrom, array.get(swapTo)));

            swapFrom = swapTo = -1;
        }
    }

    public void box(int from, int to)
    {
        boxFrom = from;
        boxTo = to;
    }

    public void copy(int from, int to, boolean copyToAux)
    {
        //Do nothing, implement only for MergeSorters!
    }

    @Override public String toString()
    {
        return sorter.getType().name();
    }

    public static List<SortDataModel> getModels()
    {
        return models;
    }

    private static void register(@NotNull final SortDataModel model)
    {
        if (models == null) {
            models = new ArrayList<SortDataModel>();
        }

        models.add(model);
    }

    private static final Comparator<Integer> COMPARATOR = new Comparator<Integer>()
    {
        public int compare(Integer first, Integer second)
        {
            return first.compareTo(second);
        }
    };

    static
    {
        register(new RandomDataModel());
        register(new AscendingDataModel(0));
        register(new DescendingDataModel(0));
        register(new AscendingDataModel(4));
        register(new DescendingDataModel(4));
        register(new TriangleDataModel(false));
        register(new TriangleDataModel(true));
        register(new RandomManyDups());
    }

    private static List<SortDataModel> models;

    protected static final int SPACE = 4;
}